using System;
using System.Collections.Generic;
using Headsight.BPMN2WF.BPMN.Model;

namespace Headsight.BPMN2WF.BPMN.BlockModel
{
    /// <summary>
    /// Traverse a <see cref="Model.BPMNFlowNodesBlock"/> and tries to segment/slice/transform it into
    /// <see cref="BPMNFlowNodesBlock"/>s with at most one <i>incoming flow</i> and
    /// at most one <i>outgoing flow</i>.
    /// </summary>
    public class BPMNFlowNodesBlock2BlockTransformerImpl : IBPMNFlowNodesBlock2BlockTransformer
    {
        public BPMNFlowNodesBlock Transform(BPMNFlowNodesBlock originalBpmnFlowNodesBlock)
        {
            var result = new BPMNFlowNodesBlock(originalBpmnFlowNodesBlock.FlowNodeIn)
                             {BPMNSequenceFlowsTest = originalBpmnFlowNodesBlock.BPMNSequenceFlows};

            CreateBlock(result, originalBpmnFlowNodesBlock.FlowNodeIn, originalBpmnFlowNodesBlock);

            return result;
        }

        private static void CreateBlock(BPMNFlowNodesBlock result, BPMNFlowNode flowNodeStart, BPMNFlowNodesBlock orgBpmnFlowNodesBlock)
        {

            var newGraphModel = orgBpmnFlowNodesBlock;

            var prevFlowNodesNextFlowNodes = newGraphModel.GetTargetBPMNFlowNodes(flowNodeStart);

            var created = new List<BPMNFlowNode>();
            foreach (var eachNextFlowNode in prevFlowNodesNextFlowNodes)
            {
                if (newGraphModel.GetNumberOfIncomingFlows(eachNextFlowNode) > 1 && !(flowNodeStart is BPMNFlowNodesBlock))
                {
                    continue;
                }
                if (newGraphModel.GetNumberOfIncomingFlows(flowNodeStart) > 1)
                {
                    continue;
                }

                var childFlowNode = CreateFlowNode(flowNodeStart, eachNextFlowNode, newGraphModel);

                created.Add(childFlowNode);

                result.AddFlow(flowNodeStart, childFlowNode);

                newGraphModel = newGraphModel.Reduce(childFlowNode);
                
                CreateBlock(result, childFlowNode, newGraphModel);
            }

            foreach (var each in created)
            {
                CreateBlock(result, each, newGraphModel);
            }

            // if node == vereinigt alle vorhergehenden nodes des blocks

            var numPrevFlowNodesNextFlowNodes = orgBpmnFlowNodesBlock.GetTargetBPMNFlowNodes(flowNodeStart).Count;
            var d = result.GetNextNodesInFlowFrom(flowNodeStart).Count;
            if (numPrevFlowNodesNextFlowNodes > 0 && d == 0)
            {
                result.NodeOut = flowNodeStart;
            }
            else if (numPrevFlowNodesNextFlowNodes == 0 && d == 0)
            {
                result.NodeOut = null;
            }
        }

        private static BPMNFlowNode CreateFlowNode(BPMNFlowNode previousFlowNode, BPMNFlowNode flowNode, BPMNFlowNodesBlock bpmnProcess)
        {
            var numFlowNodesOutgoingFlows = bpmnProcess.GetTargetBPMNFlowNodes(flowNode).Count;
            //var numFlowNodesIncomingFlow = bpmnProcess.GetNumberOfIncomingFlows(flowNode);
            var numPreviousFlowNodesOutgoingFlows = bpmnProcess.GetTargetBPMNFlowNodes(previousFlowNode).Count;

            if (numFlowNodesOutgoingFlows >= 2 || numPreviousFlowNodesOutgoingFlows >= 2)
            {
                var result = new BPMNFlowNodesBlock(flowNode) {BPMNSequenceFlowsTest = bpmnProcess.BPMNSequenceFlows};
                CreateBlock(result, result.FlowNodeIn, bpmnProcess);

                return result;
            }

            return flowNode;
        }
    }
}